﻿/* MIT License
 * Copyright © 2006 The Mono.Xna Team
 * 
 * All rights reserved.
 * 
 * Authors:  * Stuart Carnie (stuart.carnie@gmail.com)
 *			 * Lars Magnusson (lavima@gmail.com)
 *			 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework.Graphics;

namespace Microsoft.Xna.Framework
{
	/// <summary>
	/// Handles the configuration and management of the graphics device.
	/// </summary>
    public class GraphicsDeviceManager : IGraphicsDeviceService, IDisposable, IGraphicsDeviceManager
    {
		private int backBufferHeight;
		private int backBufferWidth;
		Game game;
		GraphicsDevice graphicsDevice;
		int _preferredBackBufferWidth;
		int _preferredBackBufferHeight;
		private DisplayOrientation supportedOrientations;
		private bool isDeviceDirty;

		/// <summary>
		/// Specifies the default minimum back-buffer height.
		/// </summary>
		public static readonly int DefaultBackBufferHeight = 300;

		/// <summary>
		/// Specifies the default minimum back-buffer width.
		/// </summary>
		public static readonly int DefaultBackBufferWidth = 400;

        public bool IsFullScreen
        {
            get;
            set;
        }

        public void ApplyChanges()
        {
            GraphicsDevice.Viewport = new Viewport()
            {
                Width = PreferredBackBufferWidth,
                Height = PreferredBackBufferHeight,
                X = 0,
                Y = 0
            };
            GraphicsDevice.Root.Width = PreferredBackBufferWidth;
            GraphicsDevice.Root.Height = PreferredBackBufferHeight;
            GraphicsDevice.PresentationParameters.BackBufferFormat = PreferredBackBufferFormat;
            GraphicsDevice.Reset();
        }

        public GraphicsDevice GraphicsDevice
        {
			get { return this.graphicsDevice; }
        }

		#region Constructor
		/// <summary>
		/// Creates a new GraphicsDeviceManager and registers it to handle the configuration and management of the graphics device for the specified Game.
		/// </summary>
		/// <param name="game">Game the GraphicsDeviceManager should be associated with.</param>
        public GraphicsDeviceManager(Game game)
        {
			this.SynchronizeWithVerticalRetrace = true;
			this.backBufferWidth = DefaultBackBufferWidth;
			this.backBufferHeight = DefaultBackBufferHeight;
            PreferredBackBufferFormat = SurfaceFormat.Color;
			if (game == null)
			{
				throw new ArgumentNullException("game", FrameworkResources.GameCannotBeNull);
			}
			this.game = game;
			if (game.Services.GetService(typeof(IGraphicsDeviceManager)) != null)
			{
				throw new ArgumentException(FrameworkResources.GraphicsDeviceManagerAlreadyPresent);
			}
            game.Services.AddService(typeof(IGraphicsDeviceManager), this);
            game.Services.AddService(typeof(IGraphicsDeviceService), this);

			game.Window.ClientSizeChanged += new EventHandler<EventArgs>(this.GameWindowClientSizeChanged);
			game.Window.ScreenDeviceNameChanged += new EventHandler<EventArgs>(this.GameWindowScreenDeviceNameChanged);
			game.Window.OrientationChanged += new EventHandler<EventArgs>(this.GameWindowOrientationChanged);
        }
		#endregion

		public int PreferredBackBufferWidth
        {
            get
            {
                return _preferredBackBufferWidth;
            }
            set
            {
                _preferredBackBufferWidth = value;
            }
        }

        public int PreferredBackBufferHeight
        {
            get
            {
                return _preferredBackBufferHeight;
            }
            set
            {
                _preferredBackBufferHeight = value;
            }
        }

        public SurfaceFormat PreferredBackBufferFormat
        {
            get;
            set;
        }

		public DisplayOrientation SupportedOrientations
		{
			get { return this.supportedOrientations; }
			set
			{
				this.supportedOrientations = value;
				this.isDeviceDirty = true;
			}
		}

        public bool SynchronizeWithVerticalRetrace { get; set; }

		#region Protected Methods
		/// <summary>
		/// Determines whether the given GraphicsDeviceInformation is compatible with the existing graphics device.
		/// </summary>
		/// <param name="newDeviceInfo">Information describing the desired device configuration.</param>
		protected virtual bool CanResetDevice(GraphicsDeviceInformation newDeviceInfo)
		{
			return (this.graphicsDevice.GraphicsProfile == newDeviceInfo.GraphicsProfile);
		}

		/// <summary>
		/// Releases the unmanaged resources used by the GraphicsDeviceManager and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing">true to release both automatic and manual resources; false to release only manual resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				if (this.game != null)
				{
					if (this.game.Services.GetService(typeof(IGraphicsDeviceService)) == this)
					{
						this.game.Services.RemoveService(typeof(IGraphicsDeviceService));
					}
					this.game.Window.ClientSizeChanged -= new EventHandler<EventArgs>(this.GameWindowClientSizeChanged);
					this.game.Window.ScreenDeviceNameChanged -= new EventHandler<EventArgs>(this.GameWindowScreenDeviceNameChanged);
					this.game.Window.OrientationChanged -= new EventHandler<EventArgs>(this.GameWindowOrientationChanged);
				}
				if (this.graphicsDevice != null)
				{
					this.graphicsDevice.Dispose();
					this.graphicsDevice = null;
				}
				EventHandler<EventArgs> disposedHandler = this.Disposed;
				if (disposedHandler != null)
				{
					disposedHandler(this, EventArgs.Empty);
				}
			}
		}

		/// <summary>
		/// Finds the best device configuration that is compatible with the current device preferences.
		/// </summary>
		/// <param name="anySuitableDevice">true if the FindBestDevice can select devices from any available adapter; false if only the current adapter should be considered.</param>
		protected virtual GraphicsDeviceInformation FindBestDevice(bool anySuitableDevice)
		{
			GraphicsDeviceInformation gdi = new GraphicsDeviceInformation();
			gdi.Adapter = GraphicsAdapter.DefaultAdapter;
			gdi.GraphicsProfile = GraphicsProfile.Reach;
			gdi.PresentationParameters.BackBufferWidth = this.PreferredBackBufferWidth;
			gdi.PresentationParameters.BackBufferHeight = this.PreferredBackBufferHeight;
			gdi.PresentationParameters.BackBufferFormat = this.PreferredBackBufferFormat;
			gdi.PresentationParameters.IsFullScreen = this.IsFullScreen;
			return gdi;
		}

		/// <summary>
		/// Called when a device is created. Raises the DeviceCreated event.
		/// </summary>
		/// <param name="sender">The GraphicsDeviceManager.</param>
		/// <param name="args">Arguments for the DeviceCreated event.</param>
		protected virtual void OnDeviceCreated(object sender, EventArgs args)
		{
			EventHandler<EventArgs> deviceCreatedHandler = this.DeviceCreated;
			if (deviceCreatedHandler != null)
			{
				deviceCreatedHandler(sender, args);
			}
		}

		/// <summary>
		/// Called when a device is being disposed. Raises the DeviceDisposing event.
		/// </summary>
		/// <param name="sender">The GraphicsDeviceManager.</param>
		/// <param name="args">Arguments for the DeviceDisposing event.</param>
		protected virtual void OnDeviceDisposing(object sender, EventArgs args)
		{
			EventHandler<EventArgs> deviceDisposingHandler = this.DeviceDisposing;
			if (deviceDisposingHandler != null)
			{
				deviceDisposingHandler(sender, args);
			}
		}

		/// <summary>
		/// Called when the device has been reset. Raises the DeviceReset event.
		/// </summary>
		/// <param name="sender">The GraphicsDeviceManager.</param>
		/// <param name="args">Arguments for the DeviceReset event.</param>
		protected virtual void OnDeviceReset(object sender, EventArgs args)
		{
			EventHandler<EventArgs> deviceResetHandler = this.DeviceReset;
			if (deviceResetHandler != null)
			{
				deviceResetHandler(sender, args);
			}
		}

		/// <summary>
		/// Called when the device is about to be reset. Raises the DeviceResetting event.
		/// </summary>
		/// <param name="sender">The GraphicsDeviceManager.</param>
		/// <param name="args">Arguments for the DeviceResetting event.</param>
		protected virtual void OnDeviceResetting(object sender, EventArgs args)
		{
			EventHandler<EventArgs> deviceResettingHandler = this.DeviceResetting;
			if (deviceResettingHandler != null)
			{
				deviceResettingHandler(sender, args);
			}
		}

		/// <summary>
		/// Called when the GraphicsDeviceManager is changing the GraphicsDevice settings (during reset or recreation of the GraphicsDevice). Raises the PreparingDeviceSettings event.
		/// </summary>
		/// <param name="sender">The GraphicsDeviceManager.</param>
		/// <param name="args">The graphics device information to modify.</param>
		protected virtual void OnPreparingDeviceSettings(object sender, PreparingDeviceSettingsEventArgs args)
		{
			EventHandler<PreparingDeviceSettingsEventArgs> preparingDeviceSettingsHandler = this.PreparingDeviceSettings;
			if (preparingDeviceSettingsHandler != null)
			{
				preparingDeviceSettingsHandler(sender, args);
			}
		}

		/// <summary>
		/// Ranks the given list of devices that satisfy the given preferences.
		/// </summary>
		/// <param name="foundDevices">The list of devices to rank.</param>
		protected virtual void RankDevices(List<GraphicsDeviceInformation> foundDevices)
		{
		}
		#endregion

		public void ToggleFullScreen()
        {
			this.IsFullScreen = !this.IsFullScreen;
			this.game.Window.BeginScreenDeviceChange(this.IsFullScreen);
			this.game.Window.EndScreenDeviceChange("", this._preferredBackBufferWidth, this._preferredBackBufferHeight);
			this.graphicsDevice.Reset();
        }

        #region Events
		/// <summary>
		/// Raised when a new graphics device is created.
		/// </summary>
        public event EventHandler<EventArgs> DeviceCreated;

		/// <summary>
		/// Raised when the GraphicsDeviceManager is being disposed.
		/// </summary>
        public event EventHandler<EventArgs> DeviceDisposing;

		/// <summary>
		/// Raised when the GraphicsDeviceManager is reset.
		/// </summary>
        public event EventHandler<EventArgs> DeviceReset;

		/// <summary>
		/// Raised when the GraphicsDeviceManager is about to be reset.
		/// </summary>
		public event EventHandler<EventArgs> DeviceResetting;

		/// <summary>
		/// Raised when the GraphicsDeviceManager is disposed.
		/// </summary>
		public event EventHandler<EventArgs> Disposed;

		/// <summary>
		/// Raised when the GraphicsDeviceManager is changing the GraphicsDevice settings (during reset or recreation of the GraphicsDevice).
		/// </summary>
		public event EventHandler<PreparingDeviceSettingsEventArgs> PreparingDeviceSettings;
        #endregion

        void IDisposable.Dispose()
        {
			Dispose(true);
			GC.SuppressFinalize(this); 
        }

		#region IGraphicsDeviceManager Explicit Implementation
        bool IGraphicsDeviceManager.BeginDraw()
        {
            return true;
        }

		void IGraphicsDeviceManager.CreateDevice()
		{
			GraphicsDeviceInformation info = FindBestDevice(true);
			OnPreparingDeviceSettings(this, new PreparingDeviceSettingsEventArgs(info));
			graphicsDevice = new GraphicsDevice(info.Adapter, info.GraphicsProfile, info.PresentationParameters);
			
			graphicsDevice.Disposing += new EventHandler<EventArgs>(OnDeviceDisposing);
			graphicsDevice.DeviceResetting += new EventHandler<EventArgs>(OnDeviceResetting);
			graphicsDevice.DeviceReset += new EventHandler<EventArgs>(OnDeviceReset);
			OnDeviceCreated(this, EventArgs.Empty);
		}

        void IGraphicsDeviceManager.EndDraw()
        {
			graphicsDevice.Present();
        }
        #endregion IGraphicsDeviceManager Explicit Implementation

		#region Private Methods
		private void GameWindowClientSizeChanged(object sender, EventArgs e)
		{
		}

		private void GameWindowScreenDeviceNameChanged(object sender, EventArgs e)
		{
		}

		private void GameWindowOrientationChanged(object sender, EventArgs e)
		{
		}
		#endregion
	}
}
